#include "ObjReader.h"
#include "SmoothingGroupSupport.h"
#include "IndexedFace.h"
#include "TriangleList.h"
#include "ObjMesh.h"
#include "StringUtil.h"
#include "BoneReader.h"
#include "AnimationReader.h"
#include <boost/foreach.hpp>

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

ObjReader::ObjReader(IniFile* gameConfig,tstring filename):
m_FileName(filename),
m_MaterialReader(NULL),
m_GameConfig(gameConfig),
m_CurrentMesh(NULL),
m_pCurrentMaterial(NULL),
m_VertexOffset(0),
m_NormalOffset(0),
m_TexcoordOffset(0),
m_VertexList(0),
m_pCurrentSkeleton(0),
m_HasSkin(false),
m_ChangeWinding(false)
{
	tstring basedir = m_GameConfig->GetValue(_T("Export"),_T("baseDir"));
	tstring meshes = m_GameConfig->GetValue(_T("Export"),_T("meshDir"));
	tstring fullpath = basedir +_T("/")+meshes+_T("/")+filename;
	m_Input.open(fullpath.c_str());
}

ObjReader::~ObjReader(void)
{
	if ( m_MaterialReader != NULL )
		delete m_MaterialReader;
	m_MeshMap.clear();
}

DAEFloat3 ObjReader::GetCenterPoint(){
	DAEFloat3 result;
	m_MaxVertex.Add(result,m_MinVertex);
	result.Scale(result,.5f);
	return result;
}

float ObjReader::GetHeight(){
	return m_MaxVertex.y - m_MinVertex.y;
}

bool ObjReader::Read(){
	if ( !m_Input)
		return false;
	m_SmoothingGroup = new SmoothingGroup();
	
	tstring line;
	if (m_Input.is_open())
	{
		while (! m_Input.eof() )
		{
			getline (m_Input,line);
			
			if (line.find(_T("mtllib"),0) == 0 ){
				ReadMaterialPart(line);
			}else if ( line.find(_T("skeleton"),0) ==0){
				ReadSkeleton(line);
			}else if (line.find(_T("animation"),0)==0){
				ReadAnimation(line);
			}else if (line.find(_T("vb"),0) == 0){
				ReadBoneWeights(line);
			}else if ( line.find(_T("b"),0) == 0){
				ReadBone(line);
			}else if (line.find(_T("vt"),0) == 0){
				ReadTexcoord(line);
			}else if ( line.find(_T("vn"),0) == 0){
				ReadNormal(line);
			}else if ( line.find(_T("v"),0) == 0){
				ReadVertex(line);
			}else if ( line.find(_T("f"),0) == 0){
				ReadFace(line);
			}else if ( line.find(_T("g"),0) == 0){
				ReadGroup(line);
			}else if ( line.find(_T("s"),0)== 0){
				ReadSmoothingGroup(line);
			}else if ( line.find(_T("usemtl"),0) == 0){
				ReadMaterial(line);
			}else if ( line.find(_T("changewinding"),0) == 0){
				m_ChangeWinding=true;
			}
		}
		m_Input.close();
	}
	return true;
}

void ObjReader::ReadMaterialPart(tstring& line)
{
	StringUtil::Split(line,_T(" "),parseHelp);
	if ( parseHelp.size() == 2 ){
		tstring mtlFileName = parseHelp[1];
		// the material file name is relative to the filename of the obj file.
		// find the last index of '/' or '\' and prepend it to the material file
		// name.
		size_t dotIndex = mtlFileName.find_first_of(_T("."));
		if ( dotIndex != string::npos && dotIndex == 0 ){
			mtlFileName = mtlFileName.substr(1);
		}
		mtlFileName = StringUtil::GetParentDirectory(m_FileName) + L"/"+mtlFileName;
		m_MaterialReader = new MaterialReader( m_GameConfig, mtlFileName );
		m_MaterialReader->Read();
	}
}

void ObjReader::ReadSkeleton(tstring& line)
{
	m_HasSkin=true;
	StringUtil::Split(line,_T(" "),parseHelp);
	if ( parseHelp.size() == 2 ){
		
		wstring skeletonFileName = StringUtil::GetParentDirectory(m_FileName)  + L"/" +parseHelp[1];
		BoneReader br(skeletonFileName );
		br.Read();
		m_pCurrentSkeleton = br.GetSkeleton();
		m_pCurrentSkeleton->SetFileLocation(parseHelp[1]);
		if ( m_pCurrentSkeleton != NULL )
			m_HasSkin = true;
	}
}

void ObjReader::ReadAnimation(tstring& line){
	StringUtil::Split(line,_T(" "),parseHelp);
	if ( parseHelp.size() == 2 ){
		tstring basedir = m_GameConfig->GetValue(_T("Export"),_T("baseDir"));
		tstring animationdir = m_GameConfig->GetValue(_T("Export"),_T("animationDir"));
		tstring animationFileName = basedir+ _T("/")+animationdir +_T("/")+parseHelp[1];
		AnimationReader ar(animationFileName );
		ar.Read();
		if ( m_pCurrentSkeleton != NULL){
			m_pCurrentSkeleton->AddAnimation(ar.GetAnimation());
		}
		
	}
}

void ObjReader::ReadBoneWeights(tstring& line)
{
}
void ObjReader::ReadBone(tstring& line)
{
}
void ObjReader::ReadTexcoord(tstring& line)
{
	float u,v;
	int read = swscanf_s(line.c_str(),_T("vt %f %f"),&v,&u);
	if ( read == 2 ){
		DAEFloat2 tc;
		tc.x = u;
		tc.y = v;
		m_texcoords.push_back(tc);
	}
}
void ObjReader::ReadNormal(tstring& line)
{
	float x,y,z;
	int read=swscanf_s(line.c_str(),_T("vn %f %f %f"),&x,&y,&z);
	if ( read == 3 )
	{
		DAEFloat3 loc;
		loc.x = x;
		loc.y = y;
		loc.z = z;
		loc.Normalize();
		m_normals.push_back(loc);
	}
}

void ObjReader::ReadVertex(tstring& line)
{
	float x,y,z,bw1,bw2,bw3,bw4;
	int bi1,bi2,bi3,bi4;
	int read = swscanf_s(line.c_str(),_T("v %f %f %f %d %f %d %f %d %f %d %f"),&x,&y,&z,&bi1,&bw1,&bi2,&bw2,&bi3,&bw3,&bi4,&bw4);
	if (read >= 3 ){
		DAEFloat3 loc(x,y,z);
		m_vertices.push_back(loc);
		if (m_vertices.size() == 0 ){
			m_MaxVertex=loc;
			m_MinVertex=loc;
		}else{
			if ( loc.x > m_MaxVertex.x )
				m_MaxVertex.x = loc.x;
			else if ( loc.x < m_MinVertex.x )
				m_MinVertex.x = loc.x;

			if ( loc.y > m_MaxVertex.y )
				m_MaxVertex.y = loc.y;
			else if ( loc.y < m_MinVertex.y )
				m_MinVertex.y = loc.y;
			
			if ( loc.z > m_MaxVertex.z )
				m_MaxVertex.z = loc.z;
			if ( loc.z < m_MinVertex.z )
				m_MinVertex.z = loc.z;
		}
	}
	if (read >= 11 )
	{
		BoneVertex bv;
		bv.SetBoneWeights((float)bi1,bw1,(float)bi2,bw2,(float)bi3,bw3,(float)bi4,bw4);
		m_BoneVertices.push_back(bv);
	}
}

void ObjReader::ReadFace(tstring& line)
{
	StringUtil::Split(line,_T(" "),parseHelp);
	// number in parseHelp should be 3
	VertexIndexSupport* fis[3];
	if ( parseHelp.size() == 4 )
	{
		vector<tstring>::iterator it = parseHelp.begin();
		// first element is 'f', skip it.
		++it;
		int faceIndex=0;
		while( it != parseHelp.end()){
			StringUtil::Split(*it,_T("/"),parseHelp2,false);
			tstringstream svi(parseHelp2[0]);
			int absVertexIndex = -1;
			svi>>absVertexIndex;

			fis[faceIndex] = CreateVertex(parseHelp2,absVertexIndex);

			it++;
			faceIndex++;
		}
		
		if ( m_ChangeWinding )
		{
			ObjFace* face = new ObjFace(fis[0],fis[1],fis[2]);
			m_Faces.push_back(face);
		}else{
			ObjFace* face = new ObjFace(fis[2],fis[1],fis[0]);
			m_Faces.push_back(face);
		}

		/*
		IndexedFace face;
		if ( m_ChangeWinding )
		{
			face.m_Index1=fis[0];
			face.m_Index2=fis[1];
			face.m_Index3=fis[2];
		}else{
			face.m_Index1=fis[2];
			face.m_Index2=fis[1];
			face.m_Index3=fis[0];
		}
		
		// calculate tangents if necessary
		if ( m_CurrentMesh->NeedsTangents() )
		{
			CreateTangents(fis[2],fis[1],fis[0]);
		}
		// add the face to the triangle list
		m_Faces.push_back(face);
		*/
	}
}

VertexIndexSupport* ObjReader::CreateVertex(vector<tstring> &singleFace, int absVertexIndex){
	
	int tcIndex=-1;
	int nIndex = -1;
	
	VertexIndexSupport* vis = new VertexIndexSupport(3);
	vis->SetSemanticIndex(0,absVertexIndex);
	if (singleFace.size() >= 2){
		if ( singleFace[1].size() > 0 ){
			tstringstream stci(singleFace[1]);
			stci>>tcIndex;
			vis->SetSemanticIndex(1,tcIndex);
		}else
			vis->SetSemanticIndex(1,0);
	}
	if ( singleFace.size() >=3 ){
		if (singleFace[2].size() > 0){
			tstringstream sni(singleFace[2]);
			sni>>nIndex;
			vis->SetSemanticIndex(2,nIndex);
		}else
			vis->SetSemanticIndex(2,0);
	}
	m_IdList.push_back(vis);
	return vis;
	/*
	int realMeshIndex=-1;
	if ( m_SmoothingGroup->GetMeshIndex(m_CurrentSmoothingGroup,vis,&realMeshIndex)){
		// if another combination of vertex/normal/texcoord index is used , a new vertex needs
		// to be created.
		return realMeshIndex;
	}else{
		m_VertexList->AddVertexData(meshIndex,m_PositionIndexVL,&vertex);
		m_VertexList->AddVertexData(meshIndex,m_NormalIndexVL,&(this->GetNormal(nIndex)));
		m_VertexList->AddVertexData(meshIndex,m_TexcoordIndexVL,&(this->GetTexCoord(tcIndex)));
		if ( m_HasSkin){
			BoneVertex& bv = GetBoneVertex(absVertexIndex);
			m_VertexList->AddVertexData(meshIndex,m_BoneIndexIndexVL,&bv.m_BoneIndices);
			m_VertexList->AddVertexData(meshIndex,m_BoneWeightIndexVL,&bv.m_BoneWeights);
		}
		m_SmoothingGroup->AddVertexIndex(m_CurrentSmoothingGroup,vis,meshIndex);
		return meshIndex;
	}
	*/
}

void ObjReader::ReadGroup(tstring& line)
{
	StringUtil::Split(line,_T(" "),parseHelp);
	if ( parseHelp.size() >= 2){
		
		// there is a group name
		//CloseCurrentMesh(parseHelp[1]);
		if (m_Faces.size() == 0){
			m_CurrentMesh = new ObjMesh();
			m_CurrentMesh->SetName(parseHelp[1]);
			m_SmoothingGroup = new SmoothingGroup();

			vector<SEMANTIC> semantics;
			semantics.push_back(POSITION);
			semantics.push_back(NORMAL);
			semantics.push_back(TEXCOORD);

			vector<int> sizes;
			sizes.push_back(3);
			sizes.push_back(3);
			sizes.push_back(2);

			if (m_HasSkin){
				semantics.push_back(BONEINDEX);
				semantics.push_back(BONEWEIGHT);
				sizes.push_back(4);
				sizes.push_back(4);
			}

			m_VertexList = new VertexList((int)m_vertices.size()*2,semantics,sizes);
			m_PositionIndexVL = m_VertexList->GetOffset(POSITION);
			m_NormalIndexVL = m_VertexList->GetOffset(NORMAL);
			m_TexcoordIndexVL = m_VertexList->GetOffset(TEXCOORD);
			if ( m_HasSkin){
				m_BoneIndexIndexVL = m_VertexList->GetOffset(BONEINDEX);
				m_BoneWeightIndexVL = m_VertexList->GetOffset(BONEWEIGHT);
			}
			
			m_ChangeWinding=false;
		}else{
			CloseCurrentSubgroup();
		}
		
	}else{
		if ( m_CurrentMesh != NULL ){
			CloseCurrentSubgroup();
			CloseCurrentMesh();
		}

		m_CurrentMesh = NULL;
		//m_NormalOffset+=m_normals.size();
		m_normals.clear();
		//m_TexcoordOffset+=m_texcoords.size();
		m_texcoords.clear();
		//m_VertexOffset+=m_vertices.size();
		m_vertices.clear();
		m_BoneVertices.clear();
		m_Faces.clear();
		delete m_SmoothingGroup;
		m_pCurrentSkeleton = NULL;
		m_pCurrentMaterial = NULL;
		m_HasSkin = false;
	}
}

void ObjReader::ReadSmoothingGroup(tstring& line)
{
	StringUtil::Split(line,_T(" "),parseHelp);
	
	if ( parseHelp.size() == 2 ){
		tstringstream si(parseHelp[1]);
		si>>m_CurrentSmoothingGroup;
	}
	//OutputDebugStr("ReadSmoothingGroup");
}
void ObjReader::ReadMaterial(tstring& line)
{
	StringUtil::Split(line,_T(" "),parseHelp);
	if ( parseHelp.size() == 2){
		tstring materialName = parseHelp[1];
		if ( m_MaterialReader->HasMaterial(materialName)){
			m_pCurrentMaterial = m_MaterialReader->GetMaterial(materialName);
			//m_pCurrentMaterial->Compile(m_Device);
			m_pCurrentMaterial->SetUseVertexSkinning(m_HasSkin);

			if ( m_pCurrentMaterial->IsNormalMapTextureSet() ){
				m_VertexList->AddSemantic(TANGENT,3);
				m_TangentIndexVL = m_VertexList->GetOffset(TANGENT);
			}
		}
	}
}

void ObjReader::CreateSortedVertexList(){
	m_SortedIdList.clear();
	m_SortedIdList.assign( m_IdList.begin(), m_IdList.end() );
	std::sort(m_SortedIdList.begin(), m_SortedIdList.end(),VertexIndexSupportSorter());

	int vertexId = m_VertexList->GetSize()-1;
	VertexIndexSupport * current = NULL;
	BOOST_FOREACH(VertexIndexSupport* vis, m_SortedIdList)
	{
		if ( vis->Equals(current) ){
			vis->SetIdInVertexBuffer(vertexId);
		}else{
			vertexId++;
			vis->SetIdInVertexBuffer(vertexId);
		
			DAEFloat3 vertex = m_vertices[vis->GetSemanticIndex(0)-1];
			DAEFloat3 normal = m_normals[vis->GetSemanticIndex(2)-1];
			DAEFloat2 tc = m_texcoords[vis->GetSemanticIndex(1)-1];
			m_VertexList->AddVertexData(vertexId,m_PositionIndexVL,&vertex);
			m_VertexList->AddVertexData(vertexId,m_NormalIndexVL,&normal);
			m_VertexList->AddVertexData(vertexId,m_TexcoordIndexVL,&tc);
			if (m_HasSkin && m_BoneVertices.size()  == m_vertices.size())
			{
				BoneVertex bv = m_BoneVertices[vis->GetSemanticIndex(0)-1];
				DAEFloat4 boneids = bv.m_BoneIndices;
				m_VertexList->AddVertexData(vertexId,m_BoneIndexIndexVL,&boneids);
				DAEFloat4 boneweights = bv.m_BoneWeights;
				m_VertexList->AddVertexData(vertexId,m_BoneWeightIndexVL,&boneweights);
			}
			current = vis;
		}
	}

}

void ObjReader::CloseCurrentSubgroup(){
	TriangleList * tl = new TriangleList((int)m_Faces.size());
	CreateSortedVertexList();

	BOOST_FOREACH(ObjFace* face,m_Faces)
	{
		unsigned short v1 = face->GetV1Index();
		unsigned short v2 = face->GetV2Index();
		unsigned short v3 = face->GetV3Index();
		tl->AddIndexedFace(v1,v2,v3);
	}

	MaterialTriangleList mtl;
	mtl.materialName = m_pCurrentMaterial->GetMaterialName();
	mtl.triangleList = tl;
	m_CurrentTriangleLists.push_back(mtl);

	IndexBufferMaterial* ibm= new IndexBufferMaterial(tl,m_pCurrentMaterial);
	m_CurrentMesh->AddIndexBuffer(ibm);

	
	m_IdList.clear();

	BOOST_FOREACH(ObjFace* face,m_Faces)
	{
		delete face;
	}
	m_Faces.clear();
}

void ObjReader::CloseCurrentMesh(){
	// TODO  Compile vertexbuffer
	m_CurrentMesh->SetVertexList(m_VertexList);

	AABBMesh * mesh = new AABBMesh(m_MinVertex,m_MaxVertex);
	m_CurrentMesh->SetAABBMesh(mesh);
	
	pair<tstring,ObjMesh*> p(m_CurrentMesh->GetName(),m_CurrentMesh);
	m_MeshMap.insert(p);
	m_Meshes.push_back(m_CurrentMesh);

	if (m_HasSkin)
		m_CurrentMesh->SetSkeleton(m_pCurrentSkeleton);

	vector<MaterialTriangleList>::const_iterator it;
	
	m_CurrentTriangleLists.clear();
}

DAEFloat3& ObjReader::GetVertex(int index){
	static DAEFloat3 def;
	if ( index < 0 ){
		return m_vertices[m_vertices.size()-index];
	}else{
		unsigned int transIndex = (unsigned int)(index-(int)m_VertexOffset-1);
		if( transIndex < m_vertices.size() )
			return m_vertices[transIndex];
		else
			return def;
	}
}

BoneVertex& ObjReader::GetBoneVertex(int index){
	static BoneVertex bv;
	if ( index < 0 ){
		return m_BoneVertices[m_BoneVertices.size()-index];
	}else{
		unsigned int transIndex = (unsigned int)(index-(int)m_VertexOffset-1);
		if ( transIndex < m_BoneVertices.size()) 
			return m_BoneVertices[transIndex];
		else
			return bv;
	}
}

DAEFloat3& ObjReader::GetNormal(int index){
	static DAEFloat3 def;
	if ( index < 0 ){
		return m_normals[m_vertices.size()-index];
	}else{
		unsigned int transIndex = index -m_NormalOffset - 1;
		if ( transIndex < m_normals.size() )
			return m_normals[transIndex];
		else{
			return def;
		}
	}
}

DAEFloat2& ObjReader::GetTexCoord(int index){
	static DAEFloat2 def;
	if ( index < 0 ){
		return m_texcoords[m_vertices.size()-index];
	}else{
		unsigned int transIndex = (unsigned int)(index - m_TexcoordOffset -1);
		if( transIndex < m_texcoords.size())
			return m_texcoords[transIndex];
		else 
			return def;
	}
}

ObjMesh*  ObjReader::GetMesh(tstring name){
	map<tstring,ObjMesh*>::const_iterator it = m_MeshMap.find(name);
	if( it != m_MeshMap.end()){
		return it->second;
	}else
		return NULL;
}

ObjMesh* ObjReader::GetFirstMesh(){
	if ( m_Meshes.size() > 0 )
		return m_Meshes[0];
	else
		return NULL;
}

void ObjReader::CopyIntoMeshList(MeshList* meshList){
	vector<ObjMesh*>::const_iterator it;
	for ( it = m_Meshes.begin();
		it != m_Meshes.end();
		++it){
		meshList->AddMesh(*it);
	}
	meshList->SetCenterPoint(this->GetCenterPoint());
}

void ObjReader::CreateTangents(int vertexIndex1, int vertexIndex2, int vertexIndex3)
{
	DAEFloat3 v1,v2,v3;
	DAEFloat2 w1,w2,w3;
	DAEFloat3 n1,n2,n3;

	m_VertexList->GetVertexData(vertexIndex1,m_PositionIndexVL,&v1);
	m_VertexList->GetVertexData(vertexIndex2,m_PositionIndexVL,&v2);
	m_VertexList->GetVertexData(vertexIndex3,m_PositionIndexVL,&v3);
	
	m_VertexList->GetVertexData(vertexIndex1,m_TexcoordIndexVL,&w1);
	m_VertexList->GetVertexData(vertexIndex2,m_TexcoordIndexVL,&w2);
	m_VertexList->GetVertexData(vertexIndex3,m_TexcoordIndexVL,&w3);

	m_VertexList->GetVertexData(vertexIndex1,m_NormalIndexVL,&n1);
	m_VertexList->GetVertexData(vertexIndex2,m_NormalIndexVL,&n2);
	m_VertexList->GetVertexData(vertexIndex3,m_NormalIndexVL,&n3);

	float x1 = v2.x - v1.x;
	float x2 = v3.x - v1.x;
	float y1 = v2.y - v1.y;
	float y2 = v3.y - v1.y;
	float z1 = v2.z - v1.z;
	float z2 = v3.z - v1.z;

	float s1 = w2.x - w1.x;
	float s2 = w3.x - w1.x;
	float t1 = w2.y - w1.y;
	float t2 = w3.y - w1.y;

	float r = 1.0f / (s1 * t2 - s2 * t1);

	DAEFloat3 tan1((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r );
	DAEFloat3 tan2((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r );



	DAEFloat3 tangent1;
	float n1tan1dot = n1.Dot(tan1);
	n1.Scale(tangent1,n1tan1dot);
	tan1.Subtract(tangent1,tangent1);
	tangent1.Normalize();
	
	DAEFloat3 tangent2;
	n1.Scale(tangent2,n1tan1dot);
	tan1.Subtract(tangent2,tangent1);
	tangent2.Normalize();

	DAEFloat3 tangent3;
	n1.Scale(tangent3,n1tan1dot);
	tan1.Subtract(tangent3,tangent1);
	tangent3.Normalize();

	DAEFloat3 currentT1,currentT2,currentT3;

	m_VertexList->GetVertexData(vertexIndex1,m_TangentIndexVL,&currentT1);
	m_VertexList->GetVertexData(vertexIndex2,m_TangentIndexVL,&currentT2);
	m_VertexList->GetVertexData(vertexIndex3,m_TangentIndexVL,&currentT3);
	
	tangent1.Add(tangent1,currentT1);
	tangent2.Add(tangent2,currentT2);
	tangent3.Add(tangent3,currentT3);

	m_VertexList->AddVertexData(vertexIndex1,m_TangentIndexVL,&tangent1);
	m_VertexList->AddVertexData(vertexIndex2,m_TangentIndexVL,&tangent2);
	m_VertexList->AddVertexData(vertexIndex3,m_TangentIndexVL,&tangent3);
}

/*
fn computeTangentSpace_terathon obj = (

local theMesh = snapshotAsMesh obj

local tSpace = #()

-- Do we have to flip faces?
local flip = false
local indices = #(1, 2, 3)
if dot (cross obj.transform.row1 obj.transform.row2) obj.transform.row3 <= 0 do (
indices[2] = 3
indices[3] = 2
flip = true
)

for nFace = 1 to theMesh.numFaces do (
local face = getFace theMesh nFace
local tface = getTVFace theMesh nFace

local v1 = getVert theMesh face[indices[1]]
local v2 = getVert theMesh face[indices[2]]
local v3 = getVert theMesh face[indices[3]]

local w1 = getTVert theMesh tface[indices[1]]
local w2 = getTVert theMesh tface[indices[2]]
local w3 = getTVert theMesh tface[indices[3]]

local x1 = v2.x - v1.x
local x2 = v3.x - v1.x
local y1 = v2.y - v1.y
local y2 = v3.y - v1.y
local z1 = v2.z - v1.z
local z2 = v3.z - v1.z

local s1 = w2.x - w1.x
local s2 = w3.x - w1.x
local t1 = w2.y - w1.y
local t2 = w3.y - w1.y

local r = 1.0 / (s1 * t2 - s2 * t1)

local tan1 = [(t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r]
local tan2 = [(s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r]

local normal = normalize (getFaceNormal theMesh nFace)
if flip do normal = -normal

local tangent = normalize (tan1 - normal * (dot normal tan1))

local handedness = if (dot (cross normal tan1) tan2) < 0.0 then -1.0 else 1.0

local binormal = (normalize (cross normal tangent)) * handedness

local fCenter = meshOp.getFaceCenter theMesh nFace

append tSpace (Matrix3 tangent binormal normal fCenter)
)*/